﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using Storm.TextEditor.Core;
using Storm.TextEditor.Core.Splitting;
using Storm.TextEditor.Drawing;
using Storm.TextEditor.Editor.Forms;
using Storm.TextEditor.Editor.Interacting;
using Storm.TextEditor.Editor.Text;
using Storm.TextEditor.Editor.Undo;
using Storm.TextEditor.Interacting;
using Storm.TextEditor.Interacting.Keyboard;
using Storm.TextEditor.Interacting.Scrolling;
using Storm.TextEditor.Parser.Objects;
using Storm.TextEditor.Parser.Objects.Collections;
using Storm.TextEditor.Parser.XML;
using Storm.TextEditor.Win32;
using Storm.TextEditor.Win32.Enums;

namespace Storm.TextEditor.Editor.CodeCompletion
{
	/// <summary>
	/// Represents the code completing window.
	/// </summary>
    [ToolboxItem(false)]
	public class CodeCompletionWindow
		: Control
	{
		#region Fields

		private TextEditor textEditor      = null;
		private int        tooltipDuration = 6000;	// Milliseconds.
		private string     typedText       = "";

		private ToolTip                 toolTip                = null;
		private CodeCompletionListBox   codeCompletionListBox  = null;
		private ICompletionDataProvider completionDataProvider = null;

		#endregion

		#region Properties

		/// <summary>
		/// Gets or sets the current ICompletionDataProvider of the CodeCompletion.
		/// </summary>
		public ICompletionDataProvider CompletionDataProvider
		{
			get { return completionDataProvider; }
			set
			{
				completionDataProvider = value;
				codeCompletionListBox.CompletionDataProvider = value;
				this.PopulateCompletionDataList();
			}
		}

		/// <summary>
		/// Gets or sets the duration of the tooltip.
		/// </summary>
		/// <value>The duration of the tooltip.</value>
		public int TooltipDuration
		{
			get { return tooltipDuration; }
			set { tooltipDuration = value; }
		}

		#endregion

		#region Methods

		#region Private

		/// <summary>
		/// Gets the last word.
		/// </summary>
		private string GetLastWord()
		{
			string word = "";
			int selStart = textEditor.Selection.SelStart;

			textEditor.Selection.SelStart--;
			if (textEditor.Caret.CurrentWord != null)
				word = textEditor.Caret.CurrentWord.Text;

			textEditor.Selection.SelStart = selStart;

			return word;
		}

		/// <summary>
		/// Shows the tooltip for the selected item.
		/// </summary>
		private void ShowTooltipForSelectedItem()
		{
			if (codeCompletionListBox.SelectedItem != null)
			{
				Point position = new Point(textEditor.Caret.Position.X, textEditor.Caret.Position.Y);
				position.X += (100 + this.Width + 2);
				position.Y += ((int)textEditor.ActiveViewControl.ViewPoint.RowHeight * textEditor.Caret.CurrentRow.Index) +
					((int)(Math.Ceiling((double)textEditor.ActiveViewControl.ViewPoint.RowHeight)));

				toolTip.Show(((ICompletionData)codeCompletionListBox.SelectedItem).Description, textEditor, position.X, position.Y, tooltipDuration);
				textEditor.Focus();
			}
		}

		/// <summary>
		/// Called when [text editor mouse down].
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param>
		private void OnTextEditorMouseDown(object sender, MouseEventArgs e)
		{
			this.Hide();
			toolTip.Hide(textEditor);

			textEditor.Focus();
		}

		/// <summary>
		/// Called when [list selected item changed].
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void OnListSelectedItemChanged(object sender, EventArgs e)
		{
			this.ShowTooltipForSelectedItem();
		}

		/// <summary>
		/// Called when [list double click].
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void OnListDoubleClick(object sender, EventArgs e)
		{
			if (this.Visible == true)
			{
				ICompletionData completionData = (ICompletionData)codeCompletionListBox.SelectedItem;

				int currentCharacterIndex = typedText.Length;
				while (currentCharacterIndex > 0)
				{
					textEditor.Caret.MoveLeft(true);
					currentCharacterIndex--;
				}

				textEditor.Selection.Text = "";

				TextPoint caretPosition = textEditor.Caret.Position;
				textEditor.Document.InsertText(completionData.Text, caretPosition.X, caretPosition.Y);

				// We will now have another while-loop to make our selection end
				// at the point that the inserted word ends.

				currentCharacterIndex = completionData.Text.Length;
				while (currentCharacterIndex > 0)
				{
					textEditor.Caret.MoveRight(false);
					currentCharacterIndex--;
				}

				this.Hide();
				toolTip.Hide(textEditor);
			}
		}

		/// <summary>
		/// Completes the current item.
		/// </summary>
		/// <param name="inputKey">The input key.</param>
		private void CompleteCurrentItem(char inputKey)
		{
			if (this.Visible == true && inputKey != 'm') // Somehow 'm' is registered as a completion character.
			{
				if (codeCompletionListBox.SelectedItem == null)
				{
					this.Hide();
					return;
				}

				ICompletionData completionData = (ICompletionData)codeCompletionListBox.SelectedItem;

				if (inputKey != (char)Keys.Enter)
					textEditor.Caret.MoveLeft(false);

				int currentCharacterIndex = typedText.Length;
				while (currentCharacterIndex > 0)
				{
					textEditor.Caret.MoveLeft(true);
					currentCharacterIndex--;
				}

				textEditor.Selection.Text = "";

				TextPoint caretPosition = textEditor.Caret.Position;
				textEditor.Document.InsertText(completionData.Text, caretPosition.X, caretPosition.Y);

				// We will now have another while-loop to make our selection end
				// at the point that the inserted word ends.

				currentCharacterIndex = completionData.Text.Length;
				while (currentCharacterIndex > 0)
				{
					textEditor.Caret.MoveRight(false);
					currentCharacterIndex--;
				}

				if (inputKey != (char)Keys.Enter)
					textEditor.Caret.MoveRight(false);

				this.Hide();
				toolTip.Hide(textEditor);
			}
		}

		/// <summary>
		/// Compares the completion datas.
		/// </summary>
		/// <param name="a">The first completion data.</param>
		/// <param name="b">The second completion data.</param>
		private static int CompareCompletionData(ICompletionData a, ICompletionData b)
		{
			if (a == null)
				throw new ArgumentNullException("a");

			if (b == null)
				throw new ArgumentNullException("b");

			return a.Text.CompareTo(b.Text);
		}

		#endregion

		#region Public

		/// <summary>
		/// Populates the CodeCompletionWindow's list of CompletionData based on the current ICompletionDataProvider.
		/// </summary>
		public void PopulateCompletionDataList()
		{
			codeCompletionListBox.Items.Clear();

			CompletionDataCollection createdCompletionData = this.CompletionDataProvider.CreateCompletionData();
			createdCompletionData.Sort(CompareCompletionData);

			foreach (ICompletionData completionData in createdCompletionData)
				codeCompletionListBox.Items.Add(completionData);
		}

		#endregion

		#region Internal

		/// <summary>
		/// Updates the CodeCompletionWindow based on the given input.
		/// </summary>
		/// <param name="inputKey">The input key.</param>
		/// <param name="control">if set to <c>true</c> [control].</param>
		/// <param name="alt">if set to <c>true</c> [alt].</param>
		/// <param name="shift">if set to <c>true</c> [shift].</param>
		internal void UpdateInput(char inputKey, bool control, bool alt, bool shift)
		{
			switch (inputKey)
			{
				case (char)41:
					if (shift == true)
					{
						this.CompleteCurrentItem(')');

						this.Hide();
						toolTip.Hide(textEditor);
					}

					break;
				case (char)Keys.Up:
					if (codeCompletionListBox.SelectedIndex > 0)
						codeCompletionListBox.SelectedIndex--;

					break;
				case (char)Keys.Down: // Somehow Keys.Down and ( both equals 40 when typecasted to int, so handle both.
					if (shift == true)
					{
						this.CompleteCurrentItem('(');
						this.ShowTooltipForSelectedItem();
					}
					else if (codeCompletionListBox.SelectedIndex < codeCompletionListBox.Items.Count - 1)
						codeCompletionListBox.SelectedIndex++;
					
					break;
				case '?':
				case '"':
				case ';':
				case '+':
				case '-':
				case '*':
				case '.':
				case '\'':
				case ':':
				case '_':
				case ',':
				case (char)Keys.Tab:
				case (char)Keys.Enter:
				case (char)Keys.Space:
					this.CompleteCurrentItem(inputKey);

					break;
				default:
					int fontHeight = (int)textEditor.FontSize;
					
					typedText = this.GetLastWord();
					if (typedText == "")
					{
						this.Hide();
						toolTip.Hide(textEditor);

						break;
					}

					Point position = new Point(textEditor.Caret.Position.X, textEditor.Caret.Position.Y);
					position.Y += ((int)textEditor.ActiveViewControl.ViewPoint.RowHeight * textEditor.Caret.CurrentRow.Index) + 
						((int)(Math.Ceiling((double)textEditor.ActiveViewControl.ViewPoint.RowHeight)));

					position.X += 100;
					this.Location = position;

					codeCompletionListBox.SelectedIndex = -1;
					foreach (object item in codeCompletionListBox.Items)
					{
						ICompletionData completionData = (ICompletionData)item;
						if (completionData.Text.ToLower().StartsWith(typedText.ToLower()) == true)
						{
							codeCompletionListBox.SelectedItem = item;
							this.ShowTooltipForSelectedItem();
							this.Show();

							break;
						}
					}

					break;
			}
		}

		#endregion

		#endregion

		/// <summary>
		/// Initializes a new instance of the <see cref="CodeCompletionWindow"/> class.
		/// </summary>
		public CodeCompletionWindow(TextEditor textEditor)
		{
			this.textEditor = textEditor;
			this.textEditor.MouseDown += OnTextEditorMouseDown;

			this.Cursor = Cursors.Default;
			this.Size = new Size(243, 177);
			this.Font = new Font("Segoe UI", 8);

			toolTip = new ToolTip();
			
			codeCompletionListBox = new CodeCompletionListBox();
			codeCompletionListBox.Dock = DockStyle.Fill;
			codeCompletionListBox.Font = this.Font;
			codeCompletionListBox.SendToBack();

			codeCompletionListBox.DoubleClick += OnListDoubleClick;
			codeCompletionListBox.SelectedIndexChanged += OnListSelectedItemChanged;

			this.Controls.Add(codeCompletionListBox);
		}
	}
}
